#ifndef AVALON_SIGNALSINK_HPP
#define AVALON_SIGNALSINK_HPP
#include <deque>
#include <vector>
#include <boost/any.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/preprocessor/repetition/enum_params.hpp>
#include <boost/preprocessor/repetition/enum_binary_params.hpp>
#include <boost/preprocessor/repetition/repeat.hpp>
#include <boost/preprocessor/iteration/local.hpp>
#include <boost/signals2.hpp>
#include <boost/thread/condition_variable.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/type_traits/remove_pointer.hpp>
#include <boost/type_traits/remove_reference.hpp>
#include "Avalon/SignalHandling/SignalHandling.hpp"

namespace Avalon {
namespace SignalHandling {

  /*! \class SignalSink
   *  \brief Stores signals and their parameters.
   *  \author Kamal Mansouri <kranar@hotmail.com>
   */
  class SignalSink : public boost::enable_shared_from_this<SignalSink> {
    public:

      /*! \struct SignalEntry
       *  \brief Stores the contents of a signal.
       */
      struct SignalEntry {

        //! The type of signal.
        const std::type_info* m_type;

        //! Stores the signal's parameters.
        std::vector<boost::any> m_parameters;

        //! Constructs a SignalEntry.
        /*!
          \param type The type of signal.
          \param parameters The tuple containing the signal's parameters.
        */
        SignalEntry(const std::type_info& type,
          const std::vector<boost::any>& parameters);
      };

      //! Creates a SignalSink.
      static SignalSinkPtr Create();

      ~SignalSink();

      //! Returns the next signal.
      /*!
        \param timeout The amount of time to wait for the next signal.
        \return The next signal queued.
      */
      SignalEntry GetNextSignal(boost::posix_time::time_duration timeout);

      //! Returns a slot compatible with this signal handler.
      /*!
        \return A slot compatible with this signal handler.
      */
      template<typename Signal>
      typename Signal::slot_type GetSlot() {
        return GetSlotImplementation<Signal>::Invoke(this).track(
          shared_from_this());
      }

    private:
      boost::mutex m_mutex;
      boost::condition_variable m_signalEmptyCondition;
      std::deque<SignalEntry> m_signalEntries;

      SignalSink();
      void Slot();

      template<typename Signal, int Arity = Signal::arity>
      struct GetSlotImplementation {};

      template<typename Signal>
      struct GetSlotImplementation<Signal, 0> {
        static typename Signal::slot_type Invoke(SignalSink* sink) {
          return boost::bind(
            static_cast<void (SignalSink::*)()>(&SignalSink::Slot), sink);
        }
      };

      #define AVALON_SIGNAL_SINK_PARAMETER_COUNT 10

      #define AVALON_SIGNAL_SINK_PARAMETERS(z, n, a)                           \
        parameters.push_back(a ## n);

      #define AVALON_SIGNAL_SINK_TYPE(z, n, a)                                 \
        BOOST_PP_COMMA_IF(n) typename Signal::template arg<n>::type

      #define AVALON_SIGNAL_SINK_TUPLE_MEMBERS(z, n, a)                        \
        BOOST_PP_COMMA_IF(n) typename boost::remove_reference<a##n>::type

      #define AVALON_SIGNAL_SINK_PLACEHOLDERS(z, n, a)                         \
        BOOST_PP_COMMA_IF(n) boost::arg<n + 1>()

      #define AVALON_SIGNAL_SINK_GET_TUPLE(z, n, a)                            \
        BOOST_PP_COMMA_IF(n) boost::get<n>(a)

      #define BOOST_PP_LOCAL_MACRO(n)                                          \
      template<typename Signal>                                                \
      struct GetSlotImplementation<Signal, n> {                                \
        static typename Signal::slot_type Invoke(SignalSink* sink) {           \
          return boost::bind(&SignalSink::Slot<BOOST_PP_REPEAT(n,              \
            AVALON_SIGNAL_SINK_TYPE, a)>, sink, BOOST_PP_REPEAT(n,             \
            AVALON_SIGNAL_SINK_PLACEHOLDERS, a));                              \
        }                                                                      \
      };                                                                       \
                                                                               \
      template<BOOST_PP_ENUM_PARAMS(n, typename A)>                            \
      void Slot(BOOST_PP_ENUM_BINARY_PARAMS(n, A, a)) {                        \
        boost::lock_guard<boost::mutex> lock(m_mutex);                         \
        std::vector<boost::any> parameters;                                    \
        BOOST_PP_REPEAT(n, AVALON_SIGNAL_SINK_PARAMETERS, a);                  \
       m_signalEntries.push_back(SignalEntry(typeid(                           \
          typename boost::signals2::signal<void (BOOST_PP_ENUM_PARAMS(n, A))>),\
          parameters));                                                        \
        if(m_signalEntries.size() == 1) {                                      \
          m_signalEmptyCondition.notify_all();                                 \
        }                                                                      \
      }

      #define BOOST_PP_LOCAL_LIMITS                                            \
        (1, AVALON_SIGNAL_SINK_PARAMETER_COUNT)
      #include BOOST_PP_LOCAL_ITERATE()
  };
}
}

#endif // AVALON_SIGNALSINK_HPP
